#
# etree: an ElementTree extension that is (somewhat) compatible with lxml
#
# Much of this code is copied and modified from Fredrik Lundh's 
# ElementTree package (http://effbot.org/zone/element-index.htm)
# version 1.3.0, as distributed with Python 2.7.2.
# This is not (yet) a complete re-implementation, just enough to
# parse and write simple xml documents with an interface compatible
# with with Stefan Behnel's lxml package (http://lxml.de/).
# In particular, it does not support parameter entity references,
# validation, or canonical serialization.
#
# Copyright (c) 2011 by Sean Sherrard.  All rights reserved.
#

from __future__ import absolute_import, unicode_literals

__all__ = ['Element', 'SubElement', 'ElementTree', 'XMLParser', 'TreeBuilder',
		   'iselement', 'Entity', 'Comment', 'PI', 'CDATA', 'set_default_factory',
		   'parse', 'XML', 'fromstring', 'tostring']

import io
# import webio
import os

try:
	import xml.etree.cElementTree as ET
	ETREE_VERSION = 'cElementTree %s' % ET.VERSION
except ImportError:
	import xml.etree.ElementTree as ET
	ETREE_VERSION = 'ElementTree %s' % ET.VERSION

from xml.etree.ElementTree import (Comment, PI, QName, HTML_EMPTY, iselement,
								   ElementTree as _ElementTree)
from xml.parsers import expat
EXPAT_VERSION = expat.EXPAT_VERSION.replace('_', ' ')

_xml_uri = '{http://www.w3.org/XML/1998/namespace}'
_xmlns_uri = '{http://www.w3.org/2000/xmlns}'
_default_nsmap = {_xml_uri[1:-1]: 'xml',
				  _xmlns_uri[1:-1]: 'xmlns',
				  None: None,  # special entry for default namespace
				  int: 0}      # special entry for temporary namespace count

def set_default_factory(module=ET):
	global _Element, _SubElement, _TreeBuilder
	_Element = module.Element
	_SubElement = module.SubElement
	_TreeBuilder = module.TreeBuilder

set_default_factory()

def parse(source, parser=None, base_url=None):
	tree = ElementTree()
	tree.parse(source, parser, base_url)
	return tree

def fromstring(text, parser=None, base_url=None):
	if parser is None:
		parser = XMLParser()
	parser.feed(text)
	return parser.close()

XML = fromstring

def tostring(elem, encoding=None, method=None,
			 # lxml arguments
			 xml_declaration=None, pretty_print=False,
			 with_tail=True, standalone=None):
	if not iselement(elem):
		raise TypeError('%r object is not an Element' % type(elem).__name__)
	if method is None:
		method = 'xml'
	elif method not in ('xml', 'html', 'text'):
		raise ValueError('unknown method %r' % method)
	if encoding is None:
		encoding = 'US-ASCII'
	buf = io.StringIO()
	write = buf.write
	if method == 'xml':
		_serialize_xmldecl(None, xml_declaration, encoding, standalone, write, pretty_print)
	_serialize_elem(elem, write, pretty_print, method)
	if with_tail and elem.tail:
		write(elem.tail)
	text = buf.getvalue()
	buf.close()
	if encoding is unicode:
		return text
	else:
		return text.encode(encoding=encoding, errors='xmlcharrefreplace')

#	data = []
#	class empty: pass
#	buf = empty()
#	buf.write = data.append
#	ElementTree(elem).write(buf, encoding=encoding, method=method,
#							xml_declaration=xml_declaration, pretty_print=pretty_print,
#							with_tail=with_tail, standalone=standalone)
#	return ''.join(data)

class _Element(object):
	def __init__(self, xname, prefix=None, attrib=None,

def _parse_tag(tag, nsmap):
	revmap = lambda uri: {uri: pref for pref, uri in nsmap.iteritems()}.get(uri)
							if nsmap else None
	if not isinstance(tag, basestring):
		uri, local = tag
		prefix = revmap(uri)
	elif tag[0] == '{':
		uri, local = tag[1:].split('}')
		prefix = revmap(uri)
	elif ':' in tag:
		prefix, local = tag.split(':')
		uri = nsmap[pref]
	else:
		prefix = uri = None
		local = tag
	return (uri, local), prefix

def Element(tag, attrib=None, nsmap=None, **extra):
	if attrib is None:
		attrib = extra
	else:
		attrib.update(extra)
	return _Element(xname=(uri, local), prefix=prefix, attrib=attrib, nsdecl=nsmap)

def Element(tag, attrib=None, nsmap=None, **extra):
	if attrib is not None:
		elem = _Element(tag, attrib, **extra)
	else:
		elem = _Element(tag, **extra)
	if nsmap is not None:
		for prefix, uri in nsmap.iteritems():
			elem.set(_xmlns_uri + (prefix or ''), uri or '')
	return elem

def SubElement(parent, tag, attrib=None, nsmap=None, **extra):
	if attrib is not None:
		elem = _SubElement(parent, tag, attrib, **extra)
	else:
		elem = _SubElement(parent, tag, **extra)
	if nsmap is not None:
		for prefix, uri in nsmap.iteritems():
			elem.set(_xmlns_uri + (prefix or ''), uri or '')
	return elem

def CDATA(text=None):
	elem = _Element(CDATA)
	elem.text = text
	return elem

def Entity(name):
	elem = _Element(Entity)
	if isinstance(name, int):
		elem.text = '#%d' % name
	else:
		elem.text = name
	return elem

class ElementTree(_ElementTree):

	def __init__(self, elem=None, file=None,
				 # lxml argument
				 parser=None):
		if elem is not None and file is not None:
			raise ValueError('constructor takes either element or file, not both')
		if elem is not None:
			self._setroot(elem)
		elif file is not None:
			self.parse(file, parser)
		else:
			self._root = self._doc = self._dtd = None

	@property
	def docinfo(self):
		return DocInfo(self) if self._root is not None else None

	def getroot(self):
		return self._root

	def _setroot(self, elem):
		if not iselement(elem):
			raise TypeError('%r object is not an Element' % type(elem).__name__)
		self._root = elem
		self._doc = self._dtd = None

	def parse(self, source, parser=None,
			  # lxml arguments
			  base_url=None):
		if parser is None:
			parser = XMLParser()
		if hasattr(source, "read"):
			file = source
		else:
			file = open(source, "rt")  # XXX try both text and binary mode to see which is faster
		url = getattr(file, 'name', base_url)
		try:
			if hasattr(parser, 'parsefile'):
				elem = parser.parsefile(file, url)
			else:
				while True:
					data = file.read(65536)
					if not data: break
					parser.feed(data)
				elem = parser.close()
			if elem.tag is DocInfo:
				elem.set('URL', url)
				self._doc = elem
				self._root = self._dtd = None
				for elem in elem:
					if elem.tag is DocTypeDecl:
						self._dtd = elem
					elif isinstance(elem.tag, basestring):
						self._root = elem
						break
			else:
				self._root = elem
				self._doc = self._dtd = None
		finally:
			if source is not file:
				file.close()

	def write(self, dest, encoding=None, xml_declaration=None,
			  default_namespace=None, method=None,
			  # lxml arguments
			  pretty_print=False, with_tail=True, standalone=None):
		if self._root is None:
			raise ValueError('ElementTree not initialized')
		if method is None:
			method = 'xml'
		elif method not in ('xml', 'html', 'text'):
			raise ValueError('unknown method %r' % method)
		if isinstance(dest, io.TextIOBase):  # a text-mode iostream
			if encoding is None:
				encoding = dest.encoding
			elif encoding != dest.encoding:
				raise ValueError("encoding doesn't match file encoding")
			ios = dest
			close = False
		elif hasattr(dest, 'write'):  #  an old-style file-like object or a binary-mode iostream
			if encoding is None:
				encoding = 'UTF-8'
			ios = _iotextwrapper(dest, encoding=encoding, errors='xmlcharrefreplace')
			close = False
		else:  # a filename or integer file descriptor
			if encoding is None:
				encoding = 'UTF-8'
			ios = io.open(dest, 'wt', encoding=encoding, errors='xmlcharrefreplace')
			close = True
		write = ios.write
		try:
			doc, root = self._doc, self._root
			if method == 'xml':
				_serialize_xmldecl(doc, xml_declaration, encoding, standalone, write, pretty_print)
			if doc is not None:
				for elem in doc:
					_serialize_elem(elem, write, pretty_print, method)
					if elem.tail:
						write(unicode(elem.tail))
					elif pretty_print:
						write('\n')
			else:
				_serialize_elem(root, write, pretty_print, method)
				if with_tail and root.tail:  # XXX does with_tail make any sense here?
					write(unicode(root.tail))
		finally:
			if close:
				ios.close()
			else:
				ios.flush()

del _ElementTree

def _serialize_xmldecl(doc, xmldecl, encoding, standalone, write, pretty):
	# It might make sense to use the standalone flag from the document
	# if it's not specified here, but lxml doesn't do that so we won't.
	#if standalone is None:
	#	standalone = doc.standalone
	if standalone is not None:
		standalone = u' standalone="%s"' % (u'yes' if standalone else u'no')
	else:
		standalone = u''
	if xmldecl is None:
		xmldecl = standalone or encoding.upper() not in ('US-ASCII', 'UTF-8', 'UTF-16')
	if xmldecl:
		write(u'<?xml version="1.0" encoding="%s"%s?>' % (encoding, standalone))
		if doc is not None and doc.text:
			write(doc.text)
		elif pretty:
			write(u'\n')

def _serialize_elem(elem, write, pretty, method, nsmap=None):

	def escape_attrval(text):
		if u'&' in text:
			text = text.replace(u'&', u'&amp;')
		if u'<' in text:
			text = text.replace(u'<', u'&lt;')
		if u'>' in text:
			text = text.replace(u'>', u'&gt;')
		if u'"' in text:
			text = text.replace(u'"', u'&quot;')
		if u'\n' in text:
			text = text.replace(u'\n', u'&#10;')
		return text

	def escape_cdata(text):
		if u'&' in text:
			text = text.replace(u'&', u'&amp;')
		if u'<' in text:
			text = text.replace(u'<', u'&lt;')
		if u'>' in text:
			text = text.replace(u'>', u'&gt;')
		return text

	def convert_qname(qname, nsmap, nsdeclst, istag=False):
		if qname[0] != '{':
			return qname
		uri, loc = qname[1:].rsplit('}', 1)
		if istag and uri == nsmap[None]:
			return loc
		try:
			prefix = nsmap[uri]
		except KeyError:
			idx = nsmap[int]
			nsmap[int] = idx + 1
			prefix = nsmap[uri] = 'ns%d' % idx
			nsdeclst.append(('xmlns:' + prefix, uri))
		return prefix + ':' + loc

	tag, text = elem.tag, elem.text
	if method == 'text':
		if isinstance(tag, (basestring, QName)) or tag in (CDATA, Entity):
			if text:
				write(unicode(text))
			for child in elem:
				_serialize_elem(child, write, pretty, method)
				if child.tail:
					write(unicode(child.tail))
	elif not isinstance(tag, (basestring, QName)):
		tag._serialize(elem, write, pretty)
	else:
		if nsmap is None:
			nsmap = _default_nsmap
		donelst = []
		todolst = []

		# update nsmap with any namespaces declared in this element before
		# using nsmap to convert qnames below
		for name, val in elem.items():
			name = unicode(name)
			if name.startswith(_xmlns_uri):
				prefix = name[len(_xmlns_uri):]
				uri = val
				# this is somewhat obscure, in case the user re-defines a
				# ns prefix that is already defined in the document
				newmap = {}  # XXX use a dict comprehension in py3k? {k: v for k, v in nsmap.items() if v != prefix}
				for k, v in nsmap.iteritems():
					if v != prefix:
						newmap[k] = v
				nsmap = newmap
				if prefix:
					nsmap[uri] = prefix
					donelst.append(('xmlns:' + prefix, uri))
				else:
					nsmap[None] = uri
					donelst.insert(0, ('xmlns', uri))
			else:
				todolst.append((name, val))

		tag = convert_qname(unicode(tag), nsmap, donelst, True)
		for name, val in todolst:
			name = convert_qname(name, nsmap, donelst)
			if isinstance(val, QName):
				val = convert_qname(val.text, nsmap, donelst)
			else:
				val = unicode(val)
			donelst.append((name, val))
		if isinstance(text, QName):
			text = convert_qname(text.text, nsmap, donelst)
		elif text:
			text = unicode(text)

		write('<' + tag)
		for name, val in donelst:
			write(' %s="%s"' % (name, escape_attrval(val)))

		html = method == 'html'
		if not (text or len(elem) or html):
			write(u' />')
		else:
			write(u'>')
			if text:
				if html and tag.lower() in (u'script', u'style'):
					write(text)
				else:
					write(escape_cdata(text))
			for child in elem:
				_serialize_elem(child, write, pretty, method, nsmap)
				if child.tail:
					write(escape_cdata(unicode(child.tail)))
				elif pretty:
					write(u'\n')
			if not (html and tag.lower() in HTML_EMPTY):
				write(u'</%s>' % tag)

def _serialize_externid(elem, write, pretty=False, repr=False):
	publicid = elem.get('public_id') or u''
	systemid = elem.get('system_url') or u''
	if pretty: pretty = len(publicid) + len(systemid) > 40
	if publicid and systemid:
		if pretty: write(u'\n        ')
		write(u' PUBLIC "%s"' % publicid)
		if pretty: write(u'\n               ')
		write(u' "%s"' % systemid)
	elif publicid:
		if pretty: write(u'\n        ')
		write(u' PUBLIC "%s"' % publicid)
	elif systemid:
		if pretty: write(u'\n        ')
		write(u' SYSTEM "%s"' % systemid)

class ETCompatTreeBuilder(object):

	def __init__(self, element_factory=None):
		# cElementTree.TreeBuilder() does not take an element_factory arg
		if element_factory is None:
			tb = _TreeBuilder()
		else:
			tb = _TreeBuilder(element_factory)
		self._start = tb.start
		self.data = tb.data
		self.end = tb.end
		self._close = tb.close
#		self._xmldecl = {}
		self._docelem = self._start(DocInfo, {})

	def close(self):
		self.end(DocInfo)
		return self._close()
#		doc.attrib.update(self._xmldecl)
#		return doc

	def xmldecl(self, version, encoding, standalone):
		self._docelem.set('xml_version', version)
		if encoding:
			self._docelem.set('encoding', encoding)
		if standalone >= 0:
			self._docelem.set('standalone', bool(standalone))
#		self._xmldecl = {'xml_version': version,
#						 'encoding': encoding,
#						 'standalone': bool(standalone) if standalone >= 0 else None}

	def start(self, tag, attrs=None, nsmap=None):
		if nsmap:
			for prefix, uri in nsmap.iteritems():
				attrs[_xmlns_uri + (prefix or '')] = uri or ''
		return self._start(tag, attrs)

	def comment(self, text):
		self._start(Comment, {})
		self.data(text)
		return self.end(Comment)

	def pi(self, target, data):
		self._start(PI, {})
		self.data(target + u' ' + data)
		return self.end(PI)

	def entity(self, name, is_pe=False):
		self._start(Entity, {})
		self.data(name)
		return self.end(Entity)

	def cdata(self, start):
		if start:
			self._start(CDATA, {})
		else:
			self.end(CDATA)

	def declaration(self, type, **attrs):
		if type is DocTypeDecl:
			return self._start(type, attrs) if attrs else self.end(type)
		else:
			self._start(type, attrs)
			return self.end(type)

TreeBuilder = ETCompatTreeBuilder

class ExpatParser(object):

	def __init__(self, target=None, encoding=None,
				 # lxml arguments
				 attribute_defaults=False, remove_blank_text=False,
				 resolve_entities=True, remove_comments=False,
				 remove_pis=False, strip_cdata=True):
		if target is None:
			target = TreeBuilder()
		self._target_start = target.start
		self._target_data = target.data
#		self._target_end = target.end
		self.target = target

		parser = expat.ParserCreate(encoding, '}', None)  # third arg is undocumented 'intern'
		parser.SetParamEntityParsing(expat.XML_PARAM_ENTITY_PARSING_NEVER)  # XXX for now
		parser.ExternalEntityRefHandler = self._handle_extent
		parser.XmlDeclHandler = getattr(target, 'xmldecl', None)
		if hasattr(target, 'declaration'):
			self._lastattl = {}
			def handle_decl(type, *attrs):
				targfn, dict, izip = target.declaration, dict, izip
				def handler(*args):
					targfn(type, **dict(izip(attrs, args)))  # XXX try dict comprehension in py3k: {k:v for k,v in zip(attrs,args)}
				return handler
			parser.StartDoctypeDeclHandler = handle_decl(DocTypeDecl, 'root_name', 'system_url', 'public_id', 'internal_set')
			parser.ElementDeclHandler = handle_decl(ElementDecl, 'name', 'model')
#			parser.AttlistDeclHandler = handle_decl(AttlistDecl, 'elem_name', 'attr_name', 'attr_type', 'default', 'required')
			parser.AttlistDeclHandler = self._handle_attldecl
			parser.EntityDeclHandler = handle_decl(EntityDecl, 'name', 'is_pe', 'value', 'base', 'system_url', 'public_id', 'notation')
			parser.NotationDeclHandler = handle_decl(NotationDecl, 'name', 'base', 'system_url', 'public_id')
			parser.EndDoctypeDeclHandler = handle_decl(DocTypeDecl)
		elif hasattr(target, 'doctype'):
			parser.StartDoctypeDeclHandler = lambda r, s, p, i: target.doctype(r, p, s)
		if hasattr(target, 'start_ordered'):
			parser.ordered_attributes = True
			self._nsdecl = []
			parser.StartElementHandler = self._handle_startellst
			parser.StartNamespaceDeclHandler = self._handle_startnslst
		else:
			# ElementTree won't preserve attribute order, unfortunately
			parser.ordered_attributes = False
			self._nsdecl = {}
			parser.StartElementHandler = self._handle_starteldct
			# in case someone tries to give us an ElementTree.TreeBuilder or similar
			try:
				type(target)().start('test', None, None)
			except:
				warnings.warn("Using a two argument version of TreeBuilder.start() "
							  "is deprecated and will be removed in future "
							  "versions. It should be defined as "
							  "start(self, tag, attrs=None, nsmap=None) instead.",
							  DeprecationWarning, stacklevel=2)
			else:
				parser.StartNamespaceDeclHandler = self._handle_startnsdct
		parser.EndElementHandler = target.end
		if not remove_comments:
			parser.CommentHandler = getattr(target, 'comment', None)
		if not remove_pis:
			parser.ProcessingInstructionHandler = getattr(target, 'pi', None)
		if not strip_cdata and hasattr(target, 'cdata'):
			parser.StartCdataSectionHandler = lambda: target.cdata(True)
			parser.EndCdataSectionHandler = lambda: target.cdata(False)
		if remove_blank_text:
			parser.CharacterDataHandler = self._handle_datarbt
			defhandler = self._handle_null
		else:
			parser.CharacterDataHandler = target.data
			defhandler = self._handle_default
		if not resolve_entities:
			# By setting the DefaultHandler instead of DefaultHandlerExpand,
			# all entity references should get passed unmodified to our
			# SkippedEntityHandler. However, entity references inside of
			# attribute values are still being expanded by expat.
			# From expat.h:
			#	Note: skipped parameter entities in declarations and skipped general
			#	entities in attribute values cannot be reported, because
			#	the event would be out of sync with the reporting of the
			#	declarations or attribute values
			# So this would appear to be an unavoidable design limitation.
			parser.DefaultHandler = defhandler
		else:
			parser.DefaultHandlerExpand = defhandler
		parser.SkippedEntityHandler = getattr(target, 'entity', None)
		parser.buffer_text = True
		parser.returns_unicode = True  # we want everything in Unicode
		parser.specified_attributes = not attribute_defaults
#		parser.namespace_prefixes = False  # undocumented attribute to return prefix in expanded name
		self._parser = parser
		self.version = EXPAT_VERSION
#		self._remove_blank_text = remove_blank_text

	def feed(self, data):
		if self._parser is None:
			raise ValueError('operation on a closed parser')
		self._parser.Parse(data)

	def close(self):
		if self._parser is None:
			raise ValueError('operation on a closed parser')
		self._parser.Parse(b'', True)
		elem = self.target.close()
		self.target = self._parser = None
		del self._target_start, self._target_data, self._nsdecl
		return elem

	def parsefile(self, file, url=None):
		if self._parser is None:
			raise ValueError('operation on a closed parser')
		self._parser.SetBase(url)
		self._parser.ParseFile(file)
		elem = self.target.close()
		self.target = self._parser = None
		del self._target_start, self._target_data, self._nsdecl
		return elem

#	def _closeparser(self):
#		elem = self._target.close()
#		self._target = self._parser = None
#		del self._start, self._data, self._stop, self._nsmap
#		return elem

	def _handle_dtd(self, rootname, systemid, publicid, internalset):
		self._dtd = self.target.doctype(rootname, publicid, systemid) 

#	def _handle_startdtd(self, rootname, systemid, publicid, internalset):
#		self_target.declaration(DocTypeDecl, root_name=rootname,
#								system_url=systemid, public_id=publicid,
#								internal_set=internalset)
	def _handle_enddtd(self):
		self._dtd = self.target.declaration(DocTypeDecl)
#
#	def _handle_elemdecl(self, name, model):
#		self.target.declaration(ElementDecl, name=name, model=model)
#
	def _handle_attldecl(self, elemname, attrname, attrtype, default, required):
		attrdef = (attrname, attrtype, default, required)
		if self._attl is not None and self._attl.get('name') == elemname:
			self._attl.get('attr_def').append(attrdef)
		else:
			self._attl = self.target.declaration(AttlistDecl, name=elemname,
												 attr_def=[attrdef])
#		self.target.declaration(AttlistDecl, elemname=elemname, attrname=attrname,
#								attrtype=attrtype, default=default, required=required)
#
#	def _handle_entitydecl(self, name, param, value, base, systemid, publicid, notation):
#		self.target.declaration(EntityDecl, name=name, value=value,
#								param=param, base=base, system_url=systemid,
#								public_id=publicid, notation=notation)
#
#	def _handle_notndecl(self, name, base, systemid, publicid):
#		self.target.declaration(NotationDecl, name=name, base=base,
#								system_url=systemid, public_id=publicid)

	def _handle_startnslst(self, prefix, uri):
		self._nsdecl += ((prefix, uri),)

	def _handle_startellst(self, name, attrs):
#		if name[0] != '{' and '}' in name:    # XXX try using intern dict to speed up
#			name = self._qnmap[name] = '{' + name
		if '}' in name:
			name = '{' + name
		it = iter(attrs)  # not only does this work, but it tests faster than more obvious methods
		fixattrs = [('{' + k if '}' in k else k, v) for k, v in izip(it, it)]
#		for k, v in izip(it, it):
#			fixattrs.append(('{' + k if '}' in k else k, v))
#		for k, v in attrs.iteritems():
#			fixattrs['{' + k if '}' in k else k] = v
		if self._nsdecl:
			elem = self._target_start(name, fixattrs, self._nsdecl)
			self._nsdecl = []
		else:
			elem = self._target_start(name, fixattrs)
		if self._root is None:
			self._root = elem

	def _handle_startnsdct(self, prefix, uri):
		self._nsdecl[prefix] = uri

	def _handle_starteldct(self, name, attrs):
		if '}' in name:
			name = '{' + name
		fixattrs = {}  # XXX use a dict comprehension in py3k?  {'{' + k if '}' in k else k: v for k, v in attrs.items()}
		for k, v in attrs.iteritems():
			fixattrs['{' + k if '}' in k else k] = v
		if self._nsdecl:
			elem = self._target_start(name, fixattrs, self._nsdecl)
			self._nsdecl = {}
		else:
			elem = self._target_start(name, fixattrs)
		if self._root is None:
			self._root = elem

#	def __handle_data(self, data):
#		if not (self._remove_blank_text and data.isspace()):
#			self._data(data)

	def _handle_datarbt(self, data):
		if not data.isspace():
			self._target_data(data)

	def _handle_default(self, data, linesep=unicode(os.linesep)):
		# Outside of the root element, expat reports ignorable whitespace
		# to the default handler instead of the normal data handler.
		# Also, expat doesn't translate line endings for the default
		# handler, so we must manually adjust in order to keep line
		# endings consistent.
		if data.isspace():
			self._target_data(data.replace(linesep, u'\n'))

	def _hande_null(self, *args):
		# Comments, PIs, and CDATAs will be reported here if we are
		# ignoring them. Otherwise there should be nothing left since
		# we are catching everything else in the other handlers.
		# However, I have seen some spurious text reported when expat
		# encounters complex DOCTYPE declarations. Apparently the best
		# thing to do is just ignore it.
		pass

	def _handle_extent(self, context, base, systemid, publicid):
		print (context, base, systemid, publicid)
		parser = self._parser.ExternalEntityParserCreate(context)
		from os import path
		fname = path.join(path.dirname(base), systemid)
		print fname
		file = open(fname)
		return parser.ParseFile(file)
#		# This should cause expat to raise an exception since we
#		# don't support parsing external entity references.
#		return 0

class SaxParser(object):

	def __init__(self, target=None, encoding=None,
				 #lxml arguments
				 attribute_defaults=False, remove_blank_text=False,
				 resolve_entities=True, remove_comments=False,
				 remove_pis=False, strip_cdata=True):
		pass  # XXX TODO

XMLParser = ExpatParser


def decltype(cls):
	def __init__(self, elem, elname=cls.__slots__):
		setattr(self, elname, elem)
	cls.__init__ = __init__
	def __repr__(self, elname=cls.__slots__):
		try:
			buf = io.StringIO()
			self._serialize(getattr(self, elname), buf.write, repr=True)
			return buf.getvalue().encode(encoding='utf-8')
		except:
			return '<%s>' % type(self).__name__
	cls.__repr__ = __repr__
	for attr in cls._attrs_:
		setattr(cls, attr, property(lambda self, elname=cls.__slots__, attr=attr:
										getattr(self, elname).get(attr)))
	del cls._attrs_
	return cls

@decltype
class DocTypeDecl(object):
	__slots__ = '_dtd'
	_attrs_ = ['root_name', 'system_url', 'public_id']
	externalDTD = property(lambda self: None)

	@property
	def internalDTD(self):
		if self._dtd.get('internal_set'):
			return [child.tag(child) for child in self._dtd if type(child.tag) is type]
		else:
			return None
#	internalDTD = property(lambda self: [child.tag(child) for child in self._dtd if type(child.tag) is type]
#										if self._dtd.get('internal_set') else None)
#	doctype = property(lambda self: u'<!DOCTYPE %s%s>' % (self._dtd.get('root_name'),
#						_externalid(self.public_id, self.system_url))
#						if self._dtd != {} else '')
	@property
	def doctype(self):
		if self._dtd != {}:
			buf = io.StringIO()
			DocTypeDecl._serialize(self._dtd, buf.write, repr=True)
			return buf.getvalue()
		else:
			return None

	@staticmethod
	def _serialize(elem, write, pretty=False, repr=False):
		write(u'<!DOCTYPE %s' % elem.get('root_name'))
		_serialize_externid(elem, write, pretty, repr)
		if not repr and elem.get('internal_set'):
			write(u' [')
			if elem.text:
				write(elem.text)
			elif pretty and len(elem):
				write(u'\n')
			for child in elem:
				child.tag._serialize(child, write, pretty)
				if child.tail:
					write(child.tail)
				elif pretty:
					write(u'\n')
			write(u']>')
		else:
			write(u'>')

@decltype
class ElementDecl(object):
	__slots__ = '_elem'
	_attrs_ = ['name', 'model']
	@staticmethod
	def _serialize(elem, write, pretty=False, repr=False):
		qtext = {expat.model.XML_CQUANT_OPT: u'?',
				 expat.model.XML_CQUANT_PLUS: u'+',
				 expat.model.XML_CQUANT_REP: u'*',
				 expat.model.XML_CQUANT_NONE: u''}
		def dumpmodel(model):
			type, quant, name, children = model
			if type == expat.model.XML_CTYPE_ANY:
				return u'ANY'
			elif type == expat.model.XML_CTYPE_EMPTY:
				return u'EMPTY'
			elif type == expat.model.XML_CTYPE_NAME:
				return name + qtext[quant]
			elif type == expat.model.XML_CTYPE_CHOICE:
				return u'(%s)%s' % (u' | '.join(dumpmodel(child)
							for child in children), qtext[quant])
			elif type == expat.model.XML_CTYPE_SEQ:
				return u'(%s)%s' % (u', '.join(dumpmodel(child)
							for child in children), qtext[quant])
			elif type == expat.model.XML_CTYPE_MIXED:
				return u'(#PCDATA%s)%s' % (u' | '.join([u''] + 
							[dumpmodel(child) for child in children]),
							qtext[quant])
			else:
				return u'UNKNOWN'
		write(u'<!ELEMENT %s %s>' % (elem.get('name'), dumpmodel(elem.get('model'))))

@decltype
class AttlistDecl(object):
	__slots__ = '_elem'
#	_attrs_ = ['elemname', 'attrname', 'attrtype', 'default', 'required']
	_attrs_ = ['name', 'attr_def']
	@staticmethod
	def _serialize(elem, write, pretty=False, repr=False):
		def dumpattrdef(attrdef):
			name, type, default, required = attrdef
			if default:
				val = u'%s"%s"' % (u'#FIXED ' if required else u'', default)
			else:
				val = u'#REQUIRED' if required else u'#IMPLIED'
			return u'%s %s %s' % (name, type, val)
#		required = elem.get('required')
#		default = elem.get('default')
#		if default:
#			default = u'%s"%s"' % (u'#FIXED ' if required else u'', default)
#		else:
#			default = u'#REQUIRED' if required else u'#IMPLIED'
		if repr:
			attrdef = u'... '
		elif pretty:
			attrdef = '\n        '.join(elem.get('attr_def'))
		else:
			attrdef = ' '.join(elem.get('attr_def'))
		write(u'<!ATTLIST %s %s>' % (elem.get('name'), attrdef))
#										   elem.get('attrname'),
#										   elem.get('attrtype'),
#										   default))

@decltype
class EntityDecl(object):
	__slots__ = '_elem'
	_attrs_ = ['name', 'value', 'is_pe', 'base', 'system_url', 'public_id', 'notation']
	@staticmethod
	def _serialize(elem, write, pretty=False, repr=False):
		name = elem.get('name')
		if elem.get('is_pe'):
			name = u'% ' + name
		write(u'<!ENTITY %s' % name)
		value = elem.get('value')
		if value:
			write(u' "%s"' % value)
		elif not repr:
			_serialize_externid(elem, write, pretty, repr)
		else:
			write(u' ...')
		notation = elem.get('notation')
		if notation:
			if pretty: write(u'\n        ')
			write(u' NDATA %s' % notation)
		write(u'>')

@decltype
class NotationDecl(object):
	__slots__ = '_elem'
	_attrs_ = ['name', 'base', 'system_url', 'public_id']
	@staticmethod
	def _serialize(elem, write, pretty=False, repr=False):
		write(u'<!NOTATION %s' % elem.get('name'))
		if not repr:
			_serialize_externid(elem, write, pretty, repr)
		else:
			write(u'...')
		write(u'>')

class DocInfo(object):

	def __init__(self, tree):
		self._tag = tree._root.tag
		self._doc = tree._doc if tree._doc is not None else {}
		self._dtd = tree._dtd if tree._dtd is not None else {}

	URL = property(lambda self: self._doc.get('URL'))
	xml_version = property(lambda self: self._doc.get('xml_version') or '1.0')
	encoding = property(lambda self: self._doc.get('encoding') or 'UTF-8') # could be UTF-16, but how to know?
	standalone = property(lambda self: self._doc.get('standalone'))
	doc_elem = property(lambda self: self._doc if self._doc != {} else None)
	# lxml returns the actual root element tag regardless of what the DTD
	# states, so we will do the same
	root_name = property(lambda self: self._tag)
	public_id = DocTypeDecl.public_id
	system_url = DocTypeDecl.system_url
	internalDTD = DocTypeDecl.internalDTD
	externalDTD = DocTypeDecl.externalDTD
	doctype = DocTypeDecl.doctype


CDATA._serialize = (lambda elem, write, pretty=False, repr=False:
						write(u'<![CDATA[%s]]>' % (elem.text or '')))
Entity._serialize = (lambda elem, write, pretty=False, repr=False:
						write(u'&%s;' % (elem.text or '')))
Comment._serialize = (lambda elem, write, pretty=False, repr=False:
						write(u'<!--%s-->' % (elem.text or '')))
PI._serialize = (lambda elem, write, pretty=False, repr=False:
						write(u'<?%s?>' % (elem.text or '')))

def _iotextwrapper(file, encoding, errors):
	class IOWrapper(object):
		def __init__(self, file):
			self.file = file
		def readable(self):
			return 'r' in self.file.mode
		def writable(self):
			return 'w' in self.file.mode
		def seekable(self):
			return False
		def read(self, n=-1):
			return self.file.read(n)
		def write(self, b):
			return self.file.write(b)
		def flush(self):
			return self.file.flush()
#		def close(self):  # XXX we might not want to actually close the wrapped file, maybe just call flush instead?
#			return self.file.close()
		closed = property(lambda self: self.file.closed)
	if not isinstance(file, io.IOBase):
		file = IOWrapper(file)
	newlines = None if 'b' in file.mode else ''
	return io.TextIOWrapper(file, encoding, errors, nelines, False)

del os, ET, decltype
